BPF:BCC工具 funccount 统计内核函数调用(内核函数、跟踪点USDT探针)认知
不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树
写在前面
- 博文内容涉及
BCC
工具funccount
认知 - 理解不足小伙伴帮忙指正 :),生活加油
不必太纠结于当下,也不必太忧虑未来,当你经历过一些事情的时候,眼前的风景已经和从前不一样了。——村上春树
funccount 是什么?
funccount(8)
是BCC对事件,特别是函数调用
进行计数的一个工具,可以使用它回答以下问题:
- 某个
内核态或用户态函数
是否被调用过?
1 | ┌──[root@liruilongs.github.io]-[~] |
这将统计 tcp_send_fin
函数的调用次数。如果输出显示调用次数大于 0,那就说明这个函数确实被调用过。
- 该函数每秒被调用了多少次?
1 | ┌──[root@liruilongs.github.io]-[~] |
这将每 10 秒钟输出一次 tcp_send_fin 函数的调用统计信息。从输出中我们可以计算出每秒被调用的次数,这就是该函数的调用频率。
funccount
用于自动执行 ftrace
的简单脚本。它只做一件事:内核函数计数
需要说明的是,并不是所有的函数都可以统计,判断内核函数是否可以被计数,需要检查是否在下面两个文件中。
1 | # 内核函数 |
常见的应用场景
跟踪 TCP 发送函数的调用次数
观察 TCP 发送相关函数的调用频率,可以通过下面的方式
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
tcp_send_fin
: 此函数用于发送 TCP 连接终止请求(FIN 包)
。当一方完成数据传输并想要关闭连接时,会发送 FIN 包。收到 FIN 包的一方也会发送一个 ACK 确认包,然后可能继续发送剩余的数据,最后发送自己的 FIN 包来关闭连接。
tcp_send_delayed_ack
: 此函数用于发送 TCP 延迟确认(ACK)包
。在某些情况下,TCP 协议允许在接收到数据包后不立即发送 ACK 确认包,而是等待一段时间(称为“延迟 ACK”)。此函数负责在适当的时机发送这些延迟 ACK。
tcp_send_ack
: 此函数用于发送 TCP 确认(ACK)包
。当接收到数据包时,TCP 协议要求发送方发送一个 ACK 确认包以通知接收方已成功接收数据。此函数负责发送这些 ACK 确认包。
tcp_send_mss
: 此函数用于发送 TCP 最大分段大小(MSS)选项。MSS 是 TCP 分段中可携带的最大有效载荷。在建立连接时,双方会交换 MSS 值,以便在传输过程中选择合适的分段大小。此函数负责在 SYN 包中发送本地系统的 MSS 值。
跟踪文件读写函数的调用次数
1 | ┌──[root@liruilongs.github.io]-[~] |
vfs_read
: 此函数负责在内核空间执行文件读取操作。vfs_write
: 此函数负责在内核空间执行文件写入操作
跟踪网络套接字创建和销毁函数的调用次数
1 | ┌──[root@liruilongs.github.io]-[~] |
跟踪进程创建和退出的函数调用次数
1 | ┌──[root@liruilongs.github.io]-[~] |
funccount 的示例
1 | liruilonger@cloudshell:~/bcc/tools$ cat funccount_example.txt |
funccount_example.txt
这个文档描述了 funccount
这个 eBPF/bcc
工具的使用方法和功能。
指定一个模式(正则表达式或*通配符
),追踪匹配的 函数/tracepoints
调用
1 | # ./funccount 'vfs_*' |
上面的输出显示,在跟踪vfsread()
函数时调用了 470 次,vfs_open()
264 次等等。
这对于探索内核代码非常有用,可以找出哪些函数正在使用,哪些没有使用。这可以将调查范围缩小到几个功能,这些功能的计数与所调查的工作负载类似。
统计 vfs 函数调用次数
统计所有 tcp 相关函数调用次数
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
设置统计间隔和最大时间限制,每一秒钟采样,持续时间为5s
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
过滤指定进程 ID 下的函数调用
1 | ./funccount -p 1442 contentions:* |
使用正则表达式匹配名称
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
统计指定内核态 tracepoint
静态跟踪事件调用次数
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
统计用户态 USDT 探针静态跟踪调用次数
1 | ./funccount u:pthread:mutex -p 1442 |
动态查看指定函数调用变化,每秒统计一次数据信息
1 | ./funccount -i 1 'vfs_*' |
统计单个函数指定时间内调用次数
1 | ./funccount -d 5 vfs_read |
过滤指定 CPU 下的函数调用
1 | funccount.py -i 1 -c 1 lapic_next_deadline |
funccount 的语法
funccount(8)
的命令行参数包括可以用来改变行为的选项,以及一个描述被插桩事件的字符串:
1 | funccount [opention] eventname |
eventname 的语法是:
name
或者 p:name:对内核函数 name()
进行插桩。lib:name
或者 p:1ib:name:对用户态 lib 库中的函数 name()
进行插桩。Path:name
:对位于path 路径下文件中的用户态函数 name()
进行插桩。t:system:name
:对名为system:name 的内核跟踪点
进行插桩。u:lib:name
:对 lib 库中名为name 的 USDT 探针
进行插桩。*
:用来匹配任意字符的通配符。-r 选项允许使用正则表达式。
1 | func -- probe a kernel function |
funccount 的单行程序
对虚拟文件系统 VFS 内核函数进行计数
1 | funccount 'vfs_*' |
对 TCP 内核函数进行计数
1 | funccount "tcp_*" |
统计每秒 TCP 发送函数的调用次数
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
展示每秒块 IO 事件的数量
1 | funccount -i 1 't:block;* |
展示每秒新创建的进程数量
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
展示每秒 libc
中getaddrinfo()(域名解析)函数
的调用次数
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools] |
对 libc
中 malloc()
调用进行计数:
1 | ┌──[root@liruilongs.github.io]-[/usr/share/bcc/tools/doc] |
funccount 的帮助信息
1 | liruilonger@cloudshell:~$ funccount -h |
funcccount 常见报错
1 | ┌──[root@liruilongs.github.io]-[~] |
funccount 源码
1 | #!/usr/bin/env python |
对源码进行简单分析
Probe
类提供了用于创建、配置和附加 eBPF
探测对象的方法。
**
__init__
**:初始化一个新的探测对象。解析用户提供的模式、PID 和 CPU 参数。根据模式类型(内核函数、用户空间函数、跟踪点或 USDT 探针),设置探测对象的属性。**
is_kernel_probe
**:检查探测对象是否为内核探测。如果探测类型为t
(跟踪点)或者是类型为p
(用户空间或内核函数)且库名称为空(表示内核函数),则返回True
。**
attach
**:将探测对象附加到目标上。根据探测类型(内核函数、用户空间函数、跟踪点或 USDT 探针),使用 BCC 库将 BPF 程序附加到相应的目标上。**
_add_function
**:向 BPF 程序模板中添加新的探测函数。这个方法根据给定的模板和探测名称生成一个新的探测函数,并将其添加到 BPF 程序文本中。同时,将新函数的索引和名称添加到trace_functions
字典中。**
_generate_functions
**:根据探测类型和模式生成 BPF 程序文本。这个方法根据探测类型(内核函数、用户空间函数、跟踪点或 USDT 探针)和模式,生成相应的 BPF 程序文本。对于用户空间函数和 USDT 探针,还需要处理多个地址和重复函数的问题。**
load
**:加载 BPF 程序。这个方法首先定义了一个基本的 BPF 程序模板,然后根据探测类型和模式生成具体的 BPF 程序文本。接着,使用 BCC 库将 BPF 程序加载到内核中。最后,初始化所有计数器数组项为零。**
counts
**:返回 BPF 程序的计数器数组。这个方法返回一个字典,其中键是探测位置的索引,值是对应的计数值。**
clear
**:清除所有计数器数组项。这个方法遍历trace_functions
字典,将所有计数器数组项的值重置为零。
Tool
类是 funccount
脚本的主体部分,它负责解析命令行参数、创建探测对象、加载 BPF 程序、附加探测并定期输出计数结果
**
__init__
**:初始化Tool
对象。设置命令行参数和示例,使用argparse
解析命令行参数,如目标进程的 PID、采样间隔、持续时间、是否使用正则表达式匹配函数名等。然后,根据解析得到的参数创建一个Probe
对象。**
_signal_ignore
**:静态方法,用于忽略 Ctrl+C 信号。这在脚本运行期间捕获 Ctrl+C 时很有用,因为它允许脚本在退出前完成清理工作。**
run
**:运行funccount
工具。首先,调用probe.load()
加载 BPF 程序,然后调用probe.attach()
将探测附加到目标上。接着,进入一个循环,定期输出当前的计数结果。循环将持续到达到指定的持续时间或用户按下 Ctrl+C。在循环中,根据参数设置,输出计数结果,包括时间戳、函数名和调用次数。如果达到持续时间或用户按下 Ctrl+C,脚本将停止统计,卸载 BPF 程序,并输出最后一次的计数结果。
Tool
类提供了运行 funccount
工具所需的主要功能。
博文部分内容参考
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
《BPF Performance Tools》
© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)
BPF:BCC工具 funccount 统计内核函数调用(内核函数、跟踪点USDT探针)认知